/* 
 * File:   Cliente.cpp
 * Author: Maximiliano Monzon (82341)
 * 
 * Created on October 20, 2012, 9:05 AM
 */

#include "Cliente.h"
#include "Excepcion.h"
#include "ProtocoloConstantes.h"
#include "MotorJuegoServicioConstantes.h"
#include "MundoServicioConstantes.h"
#include "PartidaServicioConstantes.h"
#include "ServicioConstantes.h"

using namespace std;
using namespace TP;
using namespace TP::Comunicacion;

Cliente::Cliente(Jugador* pJugador, Conexion* pConexion, std::ostream& pLog): _log(pLog){

    this->_jugador = pJugador;
    this->_conexion = pConexion;
    this->_partida = NULL;
}

Cliente::Cliente(Jugador* pJugador, const string& pIp, const string& pPuerto, std::ostream& pOstream): _log (pOstream){
    
    this->_jugador = pJugador;
    this->_conexion = new Conexion (pIp, pPuerto);
    this->_partida = NULL;
}

Cliente::~Cliente() {
    delete this->_conexion;
}

void Cliente::Iniciar() throw (Excepcion&){
    try
    {
        this->_conexion->Iniciar();
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("No se puede inicar el cliente.");
        throw;
    }
}

void Cliente::Finalizar() throw (Excepcion&){
    try
    {
        this->_conexion->Finalizar();
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("No se puede finalizar el cliente.");
        throw;
    }
}

void Cliente::ejecutarComando (const string& pComando) throw (Excepcion&) {
    try
    {
        this->enviarMensaje(pComando);
        
        string respuesta = "";
        this->esperarRespuesta(respuesta);
        
        this->_log << respuesta;
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("No se puede ejecutar el comando.");
        throw;
    }
}

void Cliente::ejecutarComando (const string& pComando, vector <string>& pRespuesta) throw (Excepcion&) {
    try
    {
        this->enviarMensaje(pComando);

        string respuesta = "";
        this->esperarRespuesta(respuesta);
        
        if (respuesta.length() == 0)
            throw Excepcion ("Formato de respuesta de servicio invalido.");
                    
        pRespuesta.clear();
        std::stringstream stream (respuesta);
        string item = "";
        while (std::getline(stream, item, SEPARADOR_CAMPO))
            pRespuesta.push_back(item);
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("Fallo al ejecutar el comando.");
        throw;
    }
}

void Cliente::enviarMensaje (const string& pMensaje) throw (Excepcion&){
    
    try
    {
        this->_conexion->Escribir(pMensaje);
        
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("Servicio no disponible.");
        throw;
    }
}

void Cliente::esperarRespuesta (string& pMensaje)  throw (Excepcion&) {
    try
    {
        pMensaje = "";
        this->_conexion->Leer (pMensaje);
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("Fallo al esperar el resultado de la ejecucion.");
        throw;
    }
}

void Cliente::atacar (Posicion *pPosicion) throw (Excepcion&) {
    
    //@todo: implementar cliente.atacar()
}

Partida* Cliente::crearPartida (const string& pNombreMundo, unsigned int pIdNivel, const string& pNombrePartida) throw (Excepcion&){
    
    try
    {
        // formato de comando: Partida.CrearPartida (Jugador, nombreMundo, idNivel, pNombrePartida)

        std::stringstream comando;

        comando << PARTIDA;
        comando << SEPARADOR_CAMPO;
        comando << CREAR_PARTIDA; 
        comando << SEPARADOR_CAMPO;
        comando << this->_jugador->aXML();
        comando << SEPARADOR_CAMPO;
        comando << pNombreMundo;
        comando << SEPARADOR_CAMPO;
        comando << pIdNivel;
        comando << SEPARADOR_CAMPO;
        comando << pNombrePartida;
        comando << SEPARADOR_CAMPO;
        
        vector<string> respuesta;

        this->ejecutarComando(comando.str(), respuesta);
        
        this->_partida = NULL;

        if (OK == respuesta[0])
            this->_partida = Partida::crearDeXML(respuesta[1]);
        
        else if (ERROR == respuesta[0])
            this->_log << respuesta[1] << endl;
        
        else
            throw Excepcion ("Respuesta de servicio invalida.");

        return this->_partida;
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("No se puede crear la partida.");
        throw;
    }    
}

Partida* Cliente::getPartida() throw (Excepcion&) {
    
    try
    {
        if (this->_partida)
        {
            delete this->_partida;
            this->_partida = NULL;
        }
        
        // formato de comando: MotorJuego.GetPartida (Jugador)
        
        string comando = MOTOR_JUEGO;
        comando += SEPARADOR_CAMPO;
        comando += GET_PARTIDA; 
        comando += SEPARADOR_CAMPO;
        comando += this->_jugador->aXML();
        comando += SEPARADOR_CAMPO;
        
        vector<string> respuesta;
        this->ejecutarComando(comando, respuesta);
        
        if (OK == respuesta[0])
        {
            if (NULO != respuesta[1])
                this->_partida = Partida::crearDeXML(respuesta[1]);
            else
                this->_partida = NULL;
        }
        else if (ERROR == respuesta[0])
            this->_log << respuesta[1] << endl;
        else
            throw Excepcion ("Respuesta del serivicio invalida.");
        
        return this->_partida;
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("Fallo al solicitar la partida al servidor.");
        throw;
    }
}

Partida* Cliente::unirAPartida (const string& pNombrePartida) throw (Excepcion&){
    //@todo: implementar Cliente::unirAPartida
}

vector<string> Cliente::listarPartidas () throw (Excepcion&){
    try
    {
        // Limpio la lista de partidas.
        this->_listaPartidas.clear();
        

        // formato de comando: Partida.listarPartidas ()
        
        string comando = PARTIDA;
        comando += SEPARADOR_CAMPO;
        comando += LISTAR_PARTIDAS; 
        comando += SEPARADOR_CAMPO;

        vector <string> respuesta;
        this->ejecutarComando(comando, respuesta);

        if (OK == respuesta[0])
        {
            if (NULO != respuesta[1])
                this->parsearLista(this->_listaPartidas, respuesta[1]);
        }
        else if (ERROR == respuesta[0])
            this->_log << respuesta[1] << endl;
        else
            throw Excepcion ("Respuesta de servicio invalida.");
        
        return this->_listaPartidas;
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("Fallo al obtener la lista de partidas.");
        throw;
    }    
}

vector<string> Cliente::listarMundos() throw (Excepcion&){
     try
    {
        // Inicializo la lista de mundos. 
        this->_listaMundos.clear();
         
        // formato de comando: Partida.CrearPartida (nombreJugador, nombreMundo, nombreNivel)

        string comando = MUNDO;
        comando += SEPARADOR_CAMPO;
        comando += LISTAR_MUNDOS; 
        comando += SEPARADOR_CAMPO;

        vector<string> respuesta;
        this->ejecutarComando(comando, respuesta);
       
        if (OK == respuesta[0])
            this->parsearLista(this->_listaMundos, respuesta[1]);
        
        else if (ERROR == respuesta [0])
            this->_log << respuesta[1] << endl;
        
        else
            throw Excepcion ("Formato de respuesta de servicio invalido.");
        
        return this->_listaMundos;
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("Fallo al listar mundos.");
        throw;
    }    
}

vector<string> Cliente::listarNiveles(const string &pNombreMundo) throw (Excepcion&){
    try
    {
        // Inicializo la lista de niveles.    
        this->_listaNiveles.clear();
        
        // formato de comando: Mundo.ListarNiveles(nombreMundo)

        string comando = MUNDO;
        comando += SEPARADOR_CAMPO;
        comando += LISTAR_NIVELES;
        comando += SEPARADOR_CAMPO;
        comando += pNombreMundo;
        comando += SEPARADOR_CAMPO;

        vector<string> respuesta;
        this->ejecutarComando(comando, respuesta);

        if (OK == respuesta[0])
        {
            if (NULO != respuesta[1])
                this->parsearLista(this->_listaNiveles, respuesta[1]);
        }
        else if (ERROR == respuesta[0])
            this->_log << respuesta[1] << endl;
        
        else
            throw Excepcion ("Formato de respuesta de servicio invalido.");

        return this->_listaNiveles;
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("No se puede obtener la lista de niveles.");
        throw;
    }    
}

void Cliente::parsearLista(vector<string> &partidas, const string & pDescriptor) {
    std::stringstream ssParametros (pDescriptor);
    partidas.clear();
    string item = "";
    while (std::getline(ssParametros, item, SEPARADOR_CAMPO_LISTA))
        if (item != "" )
            partidas.push_back(item);
}